<!DOCTYPE HTML>
<html lang="zh-CN">
	<head>
		<meta charset="UTF-8">
		<title>个人博客</title>
		<meta name="keywords" content="个人博客" />
		<meta name="description" content="" />
		<link rel="stylesheet" href="css/index.css" />
		<link rel="stylesheet" href="css/style.css" />
		<script type="text/javascript" src="js/jquery1.42.min.js"></script>
		<script type="text/javascript" src="js/jquery.SuperSlide.2.1.1.js"></script>
		<!--[if lt IE 9]>
<script src="js/html5.js"></script>
<![endif]-->
	</head>

	<body>
		<!--header start-->
		<div id="header">
			<h1>刘明明个人博客</h1>
			<p>青春是打开就合不上的书；人生是踏上了就回不了头的路，技术是不用就会忘记的赌注。</p>
		</div>
		<!--header end-->
		<!--nav-->
		<div id="nav">
			<ul>
				<li><a href="../index.html">首页</a></li>
				<li><a href="../learn.html" style="background: #829696;  border-radius: 3px;">学无止境</a></li>
				<li><a href="../riji.html">个人日记</a></li>
				<li><a href="../shuo.html">碎言碎语</a></li>
				<li><a href="../xc.html">相册展示</a></li>
				<li><a href="../about.html">关于我</a></li>
				<div class="clear"></div>
			</ul>
		</div>
		<!--nav end-->
		<!--content start-->
		<div id="content">
			<!--left-->
         <div class="left" id="learn">
           <div class="weizi">
           <div class="wz_text">当前位置：<a href="#">首页</a>><h1>学无止境</h1></div>
           </div>
           <div class="l_content">
					<div>
						<h1  class="title-h1" style="font-size: 20px;font-weight: 500;color: #0088db;text-align: center;padding: 10px 0;">前端监控系统之“实战篇” </h1> 
					</div>	
					<div data-v-de0d029a="" class="content"><p>&nbsp;&nbsp;&nbsp;&nbsp;决定开始动笔写实战篇的时候，望了望时间，距离上一篇文章<a href="https://www.duanliang920.com/learn/web/9153.html?isVip=1" target="_blank" style="color: rgb(70, 172, 200);">《前端监控系统之“理论篇”》</a>差不多过去4个月的时间啦，时间过得可真快。</p><p>&nbsp;&nbsp;&nbsp;&nbsp;在这期间，还有朋友留言给我：前端监控系统实战篇还会继续写吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;我回答着：写，一定会写。之所以没那么着急写，一方面：因为这段时间工作确实比较忙，没时间写。另一方面：我觉得写实战的一些文章，就应该把项目真正运用在真实的项目中，经过业务的考验和沉淀，写出来的实战篇才会有说服力。因为个人项目与企业级项目所思考的点和方向是不一样的，面对真实而复杂的场景，根本不是像写个人项目一样，草草了事实现一个功能一样。考虑的点和方面和很多，如何去设计，如何实现，如何推进...<br><br>&nbsp;&nbsp;&nbsp;&nbsp;好啦，废话就不多说啦。正式开始我们今天的话题吧！<br><br>&nbsp;&nbsp;&nbsp;&nbsp;相信看过理论篇的同学，因为会发现一些疑惑，您只是简单讲解了前端SDK上报的功能设计，和整个项目架构图，并没有涉及过多的细节。别急，今天我们详细来讲讲，具体怎么操作的。<br><br><span style="font-weight: bold; font-size: large;">一、没有前端监控下的痛点有哪些？</span><br><br>  &nbsp;&nbsp;&nbsp;&nbsp;1、用户反馈点击某个按钮没有任何反应。（没办法知道用户点了按钮，为什么没反应呢？是js报错导致，还是其它原因导致？）<br><br>  &nbsp;&nbsp;&nbsp;&nbsp;2、用户反馈打开页面很慢。（没办法感知用户慢在什么地方）<br><br>  &nbsp;&nbsp;&nbsp;&nbsp;3、调用后端api接口出错，无法第一时间感知。（只能被动等待用户告诉和自己去发现）<br><br>  &nbsp;&nbsp;&nbsp;&nbsp;4、网站静态资源加载出错，导致网站打不开。</p><p><br></p><p>&nbsp; &nbsp; 5、上报的js错误信息，怎么反解析出源代码报错位置。</p><p>  &nbsp;&nbsp;&nbsp;&nbsp;...<br><br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<img src="//cdn.duanliang920.com/uploads/article/2019-06-30/1561882677.png" style="max-width:50%;"></p><p><br><br>&nbsp;&nbsp;&nbsp;&nbsp;可能这时候相关同学就会想，此时要有一个监控系统，能帮我解决上述的痛点那该有多好。并且还希望能从后台自定义配置告警规则，通过对应的报警规则，第一时间来通知开发人员哪里出错。<br><br><img src="//cdn.duanliang920.com/uploads/article/2019-06-30/1561882798.png" style="max-width:100%;"><br><br><span style="font-weight: bold; font-size: large;">二、手动搭建一个前端监控系统</span><br><br>&nbsp;&nbsp;&nbsp;&nbsp;在搭建前端监控系统开始之前，我们先来了解下整个系统的架构是怎么样的。因为你只有了解到流程是怎样走的，才清楚每个步骤我们该做些什么事情。<br><br><img src="//cdn.duanliang920.com/uploads/article/2019-06-30/1561882923.png" style="max-width:100%;"><br><br>&nbsp;&nbsp;&nbsp;<span style="font-weight: bold; font-size: large;">&nbsp;</span><span style="font-size: medium;"><span style="font-weight: bold;">1、JS SDK </span></span><span style="font-weight: bold; font-size: medium;">功能设计</span></p><p><span style="font-size: medium;"><span style="font-weight: 700;"><br></span></span>  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color: rgb(255, 0, 0);">1.1 监控js错误信息上报</span></p><p>  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;通过window.onerror方法，对页面js错误进行监控。上报具体的错误信息和堆栈信息（如下图），利用webpack/gulp打包出来的.map文件，来解析出具体的错误信息。<br><br>&nbsp; &nbsp; &nbsp;&nbsp;<img src="//cdn.duanliang920.com/uploads/article/2019-06-30/1561883012.png" style="max-width:100%;"><br><br>&nbsp; &nbsp; &nbsp; nodejs通过上传.map文件，解析出具体错误信息。代码如下：</p><p><br></p><pre class=" language-js"><code class=" language-js"><span class="token keyword">let</span> sourceMap <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'source-map'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
					
					<span class="token keyword">let</span> _sourceMap <span class="token operator">=</span> fs<span class="token punctuation">.</span><span class="token function">readFileSync</span><span class="token punctuation">(</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token template-string"><span class="token string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">'utf-8'</span><span class="token punctuation">)</span>
					
					<span class="token comment">// 通过sourceMap库转换为sourceMapConsumer对象  </span>
					<span class="token keyword">let</span> consumer <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">sourceMap<span class="token punctuation">.</span>SourceMapConsumer</span><span class="token punctuation">(</span>_sourceMap<span class="token punctuation">)</span><span class="token punctuation">;</span>
					<span class="token comment">// 传入要查找的行列数，查找到压缩前的源文件及行列数  </span>
					<span class="token keyword">let</span> sm <span class="token operator">=</span> consumer<span class="token punctuation">.</span><span class="token function">originalPositionFor</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
					    line<span class="token punctuation">:</span> line<span class="token punctuation">,</span> column<span class="token punctuation">:</span> column
					<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
					<span class="token comment">// 压缩前的所有源文件列表  </span>
					<span class="token keyword">let</span> sources <span class="token operator">=</span> consumer<span class="token punctuation">.</span>sources<span class="token punctuation">;</span>
					<span class="token comment">// 根据查到的source，到源文件列表中查找索引位置  </span>
					<span class="token keyword">let</span> smIndex <span class="token operator">=</span> sources<span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span>sm<span class="token punctuation">.</span>source<span class="token punctuation">)</span><span class="token punctuation">;</span>
					<span class="token comment">// 到源码列表中查到源代码  </span>
					<span class="token keyword">let</span> smContent <span class="token operator">=</span> consumer<span class="token punctuation">.</span>sourcesContent<span class="token punctuation">[</span>smIndex<span class="token punctuation">]</span><span class="token punctuation">;</span>
					<span class="token comment">// 将源代码串按"行结束标记"拆分为数组形式  </span>
					<span class="token keyword">const</span> rawLines <span class="token operator">=</span> smContent<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token regex">/\r?\n/g</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
					<span class="token comment">// 输出源码行，因为数组索引从0开始，故行数需要-1  </span>
					<span class="token keyword">let</span> errInfo <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
					errInfo<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
					    line<span class="token punctuation">:</span> sm<span class="token punctuation">.</span>line <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">,</span>
					    text<span class="token punctuation">:</span> rawLines<span class="token punctuation">[</span>sm<span class="token punctuation">.</span>line <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">]</span>
					<span class="token punctuation">}</span><span class="token punctuation">)</span>
					errInfo<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
					    line<span class="token punctuation">:</span> sm<span class="token punctuation">.</span>line <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">,</span>
					    text<span class="token punctuation">:</span> rawLines<span class="token punctuation">[</span>sm<span class="token punctuation">.</span>line <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span>
					<span class="token punctuation">}</span><span class="token punctuation">)</span>
					errInfo<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
					    line<span class="token punctuation">:</span> sm<span class="token punctuation">.</span>line<span class="token punctuation">,</span>
					    text<span class="token punctuation">:</span> rawLines<span class="token punctuation">[</span>sm<span class="token punctuation">.</span>line<span class="token punctuation">]</span>
					<span class="token punctuation">}</span><span class="token punctuation">)</span> 
					
					<span class="token keyword">this</span><span class="token punctuation">.</span>ctx<span class="token punctuation">.</span>body <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
					  code<span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">,</span>
					  data<span class="token punctuation">:</span> errInfo<span class="token punctuation">,</span>
					  msg<span class="token punctuation">:</span> <span class="token string">'success'</span>
					<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre><p>&nbsp; &nbsp; 以我博客的一个js错误为例：首先在管理后台，上传对应错误js的map文件，最终还原效果图如下：</p><p><br>&nbsp; &nbsp; &nbsp; &nbsp;<img src="//cdn.duanliang920.com/uploads/article/2019-06-30/1561883170.png" style="max-width:50%;"></p><p><br></p><p>&nbsp; &nbsp;通过上图，我们可以快速定位错误的具体位置，大大节省了排查错误的时间。<br></p><p>&nbsp; &nbsp; &nbsp;&nbsp;<br>&nbsp; &nbsp;<span style="color: rgb(255, 0, 0);">1.2 api接口调用请求上报</span><br><br>&nbsp; &nbsp; 可能有些朋友会好奇，怎么去实时监听ajax异步接口请求情况呢？</p><p>&nbsp; &nbsp; 其实很简单：首先我们先通过代理XMLHttpRequest对象，来实现对ajax异步请求进行数据劫持。如下图：</p><p><br>&nbsp; &nbsp;&nbsp;<img src="//cdn.duanliang920.com/uploads/article/2019-06-30/1561883221.png" style="max-width:100%;"><br><br>&nbsp; &nbsp; 通过上图不难发现，我们通过xhr对象的onreadystatechange事件进行劫持，针对后端api接口返回的数据，进行处理上报接口的调用情况，因此达到实时监控接口调用情况，是不是觉得很神奇！</p><p><br>&nbsp; &nbsp; 如有同学想了解这块内容具体的代码，github社区有开源的模块，可自行查看： https://github.com/wendux/Ajax-hook<br>  &nbsp;&nbsp;&nbsp;&nbsp;根据需要统计哪些数据进行上报，比如:page query参数、api接口请求数据...方便后续出错，定位问题。如下图：<br><br><img src="//cdn.duanliang920.com/uploads/article/2019-06-30/1561884332.png" style="max-width:100%;"><br><br>  &nbsp;&nbsp;&nbsp;<span style="color: rgb(255, 0, 0);">&nbsp;1.3 页面性能数据上报</span><br><br>  &nbsp;&nbsp;&nbsp;&nbsp;关于页面性能数据，我们一般通过浏览器performance.timingAPI来进行上报，在这里不做过多解释（理论篇有讲过），但一般我们会关注以下的字段的关键性能指标，如下图：<br><br>&amp;nbs</p></div>
				</div>
				<!---->
				
         </div>
         <!--left end-->
         <!--right-->
         <div class="right" id="c_right">
          <div class="s_about">
          <h2>关于博主</h2>
           <img src="images/my.jpg" width="230" height="230" alt="博主"/>
           <p>博主：青牛大师</p>
           <p>职业：前端交互、java技术栈</p>
           <p>爱好：旅游、K歌</p>
           <p>简介：80后屌丝，一个草根程序员，喜欢研究web前端交互技术和java应用技术。</p>
           <div class="clear"></div>
           </p>
          </div>
          <!--栏目分类-->
           <!--end--> 
           <div class="link">
            <h2>收藏链接</h2>
            <p><a href="https://www.oschina.net/question/565065_86020">Java 数组转型和范型</a></p>
            <p><a href="https://www.oschina.net/question/3939240_2316318">Java 泛型擦除</a></p> 
            <p><a href="https://my.oschina.net/u/4001573/blog/3218016">Java之异常处理</a></p> 
            <p><a href="https://my.oschina.net/PerfMa/blog/3198061">JVM源码分析之堆外内存完全解读</a></p> 
           	<p><a href="https://my.oschina.net/OutOfMemory/blog/4272077">Dubbo分析之心跳设计</a></p>
           </div>
         </div>
         <!--right end-->
         <div class="clear"></div>
    </div>
    <!--content end-->
    <!--footer start-->
    <div id="footer">
     <p>Design by:青牛大师</p>
     <p><a href="http://www.miitbeian.gov.cn" target="_black">苏ICP备20027343号</a></p>
    </div>
    <!--footer end-->
</body>
</html>
